﻿using System;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.ServiceModel.Syndication;
using System.Text;
using System.Xml;
using NCMIS.ObjectModel.Rest;
using NCMIS.ServiceModel.Syndication;

namespace NCMIS.Client
{
    /// <summary>
    /// 
    /// </summary>
    public class CmisWebRequest
    {
        #region Properties

        public string Password
        {
            get;
            set;
        }

        public Uri ServiceDocumentUri
        {
            get;
            set;
        }

        public string UserName
        {
            get;
            set;
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Instatiates a new instance of <see cref="NCMIS.Client.CmisWebRequest"/>.
        /// </summary>
        /// <param name="userName">The username.</param>
        /// <param name="password">The password.</param>
        /// <param name="serviceDocumentUri">The service document URI.</param>
        public CmisWebRequest(string userName, string password, Uri serviceDocumentUri)
        {
            this.UserName = userName;
            this.Password = password;
            this.ServiceDocumentUri = serviceDocumentUri;
        }

        #endregion

        #region GET Methods

        /// <summary>
        /// Converts a seekable (e.g. MemoryStream) or non-seekable stream (e.g. a Response Stream from a Web Request), to a byte array.
        /// </summary>
        /// <param name="uri">The URI of the content stream.</param>
        /// <returns>A <see cref="System.Bye"/> array, holding the content stream.</returns>
        public byte[] GetContentStreamAsByteArray(Uri uri)
        {
            using (Stream stream = GetContentStream(uri))
            {
                if (stream == null)
                {
                    return System.Text.ASCIIEncoding.UTF8.GetBytes("Content stream is not available.");
                }

                MemoryStream memoryStream = new MemoryStream();
                byte[] buffer = new byte[32768];
                int bytesRead;

                while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    memoryStream.Write(buffer, 0, bytesRead);
                }
                return memoryStream.GetBuffer();
            }
        }

        /// <summary>
        /// Gets the content stream from the specified URI.
        /// </summary>
        /// <param name="uri">The URI to the content stream or null if a content stream couldn't be fetched.</param>
        /// <returns>A <see cref="System.IO.Stream"/> object.</returns>
        public Stream GetContentStream(Uri uri)
        {
            Stream stream = null;
            try
            {
                HttpWebResponse response = GetResponse(uri.AbsoluteUri);
                stream = response.GetResponseStream();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }
            return stream;
        }

        /// <summary>
        /// Gets the service document.
        /// </summary>
        /// <returns>An instance of <see cref="System.ServiceModel.Synication.ServiceDocument"/> or null if a service document couldn't be fetched.</returns>
        public ServiceDocument GetServiceDocument()
        {
            ServiceDocument serviceDocument = null;
            try
            {
                HttpWebResponse response = GetResponse(this.ServiceDocumentUri.AbsoluteUri);
                serviceDocument = ConvertResponseToServiceDocument(response);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }

            return serviceDocument;
        }

        /// <summary>
        /// Gets the syndication item at the specified URI.
        /// </summary>
        /// <param name="uriString">URI string specifying the location of the item.</param>
        /// <returns>A <see cref="System.ServiceModel.Syndication.SyndicationItem"/> object.</returns>
        public SyndicationItem GetSyndicationItem(string uriString)
        {
            SyndicationItem result = null;
            try
            {
                HttpWebResponse response = GetResponse(uriString);
                result = ConvertResponseToSyndicationItem(response);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }
            return result;
        }

        /// <summary>
        /// Gets the syndication feed at the specified URI.
        /// </summary>
        /// <param name="uriString">URI string specifying the location of the feed.</param>
        /// <returns>A <see cref="System.ServiceModel.Syndication.SyndicationFeed"/> object.</returns>
        public SyndicationFeed GetSyndicationFeed(string uriString)
        {
            SyndicationFeed result = null;
            try
            {
                HttpWebResponse response = GetResponse(uriString);
                result = ConvertResponseToSyndicationFeed(response);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }
            return result;
        }

        #endregion

        #region Delete Methods

        /// <summary>
        /// Deletes a syndication entry.
        /// </summary>
        /// <param name="uri">The URI of the entry.</param>
        public void DeleteEntry(string uri)
        {
            try
            {
                HttpWebResponse response = GetResponse(uri, "DELETE");
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Delete error: {0}", ex.Message);
            }
        }

        #endregion

        #region Invoke Methods (POST, PUT, DELETE)

        /// <summary>
        /// Invoke feed without any content in request. This method should be used with the HTTP methods POST, PUT or DELETE.
        /// GET will most likely cause an null result, since the response will be a feed, and not an item.
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="method">Valid methods are any HTTP methods, such as POST, PUT, DELETE.</param>
        /// <returns></returns>
        public SyndicationItem InvokeSyndicationFeed(string uri, string method)
        {
            SyndicationItem result = null;
            try
            {
                HttpWebResponse response = GetResponse(uri, method);
                result = ConvertResponseToSyndicationItem(response);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }
            return result;
        }

        /// <summary>
        /// Invoke feed with the specified content in request. This method should be used with the HTTP methods POST, PUT or DELETE.
        /// GET will most likely cause an null result, since the response will be a feed, and not an item.
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="entry"></param>
        /// <returns></returns>
        public SyndicationItem InvokeSyndicationFeed(string uri, SyndicationItem entry)
        {
            SyndicationItem result = null;
            try
            {
                // HTTP POST request
                var request = (HttpWebRequest)WebRequest.Create(uri);
                request.Credentials = new NetworkCredential(UserName, Password);
                request.Method = "POST";

                // contentlength is not known

                //
                request.ContentType = CmisContentType.Entry;

                using (Stream requestStream = request.GetRequestStream())
                {
                    entry.WriteTo(requestStream);
                }

                // HTTP response
                var response = (HttpWebResponse)request.GetResponse();
                result = ConvertResponseToSyndicationItem(response);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
                return null;
            }
            return result;
        }

        /// <summary>
        /// Invoke a CMIS query with the specified request.
        /// </summary>
        /// <param name="queryUri">The query collection URI of the repository</param>
        /// <param name="query">The query object</param>
        /// <returns>A feed containing the search results</returns>
        public SyndicationFeed InvokeCmisQuery(string queryUri, CmisQuery query)
        {
            byte[] content = query.ToByteArray();
            System.Diagnostics.Debug.WriteLine(Encoding.UTF8.GetString(content));

            SyndicationFeed result = null;
            try
            {
                // HTTP POST request
                var request = (HttpWebRequest)WebRequest.Create(queryUri);
                request.Credentials = new NetworkCredential(UserName, Password);
                request.Method = "POST";
                request.ContentLength = content.Length;
                request.ContentType = CmisContentType.Query;
                using (Stream requestStream = request.GetRequestStream())
                {
                    requestStream.Write(content, 0, content.Length);
                }

                // HTTP response
                var response = (HttpWebResponse)request.GetResponse();
                result = ConvertResponseToSyndicationFeed(response);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
                return null;
            }
            return result;
        }

        #endregion

        #region Helpers

        /// <summary>
        /// Helper method for retrieving the response from a request URI, using HTTP method GET.
        /// </summary>
        /// <param name="requestUriString">The request URI string to get response from.</param>
        /// <returns>A <see cref="System.Net.HttpWebRequest"/> object or null, if no response could be retreived.</returns>
        private HttpWebResponse GetResponse(string requestUriString)
        {
            return GetResponse(requestUriString, "GET");
        }

        private HttpWebResponse GetResponse(string requestUriString, string method)
        {
            HttpWebRequest request = WebRequest.Create(requestUriString) as HttpWebRequest;
            request.Credentials = new NetworkCredential(this.UserName, this.Password);
            request.Method = method;
            return request.GetResponse() as HttpWebResponse;
        }

        #endregion

        #region ConvertResponse methods

        /// <summary>
        /// Convert the specified HTTP response to an Atom service document.
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        private static ServiceDocument ConvertResponseToServiceDocument(HttpWebResponse response)
        {
            string[] contentTypes;
            string[] contentTypes2;
            ServiceDocument serviceDocument = null;

            contentTypes = response.Headers.GetValues("Content-Type");
            contentTypes2 = contentTypes[0].Split(';');
            using (XmlReader reader = XmlReader.Create(response.GetResponseStream()))
            {
                try
                {
                    if (contentTypes[0].Equals(CmisContentType.Service) || contentTypes2[0].Equals(CmisContentType.Service))
                    {
                        serviceDocument = ServiceDocument.Load(reader);
                    }
                }
                catch (XmlException ex)
                {
                    throw ex;
                }
            }
            return serviceDocument;
        }

        /// <summary>
        /// Convert the specified HTTP response to a syndication item (since other classes directly 
        /// cannot read HTTP responses.
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        private static SyndicationItem ConvertResponseToSyndicationItem(HttpWebResponse response)
        {
            string[] contentTypes;
            SyndicationItem item = null;

            contentTypes = response.Headers.GetValues("Content-Type");
            using (XmlReader reader = XmlReader.Create(response.GetResponseStream()))
            {
                try
                {
                    // There may be more text attached to end of content type string
                    if (contentTypes[0].StartsWith(CmisContentType.Entry))
                    {
                        item = SyndicationItem.Load(reader);
                    }
                }
                catch (XmlException ex)
                {
                    throw ex;
                }
            }

            return item;
        }

        /// <summary>
        /// Convert the specified HTTP response to a syndication feed. Similar to ConvertResponseToSyndicationItem
        /// but returns a feed instead of an item.
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        private static SyndicationFeed ConvertResponseToSyndicationFeed(HttpWebResponse response)
        {
            string[] contentTypes;
            SyndicationFeed feed = null;

            contentTypes = response.Headers.GetValues("Content-Type");
            using (XmlReader reader = XmlReader.Create(response.GetResponseStream()))
            {
                try
                {
                    // There may be more text attached to end of content type string
                    if (contentTypes[0].StartsWith(CmisContentType.Feed))
                    {
                        feed = SyndicationFeed.Load(reader);
                    }
                }
                catch (XmlException ex)
                {
                    throw ex;
                }
            }

            return feed;
        }

        #endregion
    }
}